/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.openbookformat.heading;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.PropertyVetoException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Observable;
import javax.swing.ActionMap;
import javax.swing.text.DefaultEditorKit;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ui.OpenProjects;
import org.openbookformat.tags.TagParsingException;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
import org.netbeans.api.settings.ConvertAsProperties;
import org.openbookformat.chaptersviewer.OBFChaptersViewerTopComponent;
import org.openbookformat.obffinder.FindResult;
import org.openbookformat.obffinder.FindUtil;
import org.openbookformat.project.BookProp;
import org.openbookformat.project.Chapter;
import org.openbookformat.project.HeadingPointer;
import org.openbookformat.tags.HeadingTag;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.cookies.EditorCookie;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.explorer.view.ListView;
import org.openide.loaders.DataObject;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.Utilities;

/**
 * Top component which displays something.
 */
@ConvertAsProperties(dtd = "-//org.openbookformat.heading//OBFHeadings//EN",
autostore = false)
@TopComponent.Description(preferredID = "OBFHeadingsTopComponent",
iconBase = "org/openbookformat/heading/heading.png",
persistenceType = TopComponent.PERSISTENCE_ALWAYS)
@TopComponent.Registration(mode = "navigator", openAtStartup = true)
@ActionID(category = "Window", id = "org.openbookformat.heading.OBFHeadingsTopComponent")
@ActionReference(path = "Menu/Window" /*, position = 333 */)
@TopComponent.OpenActionRegistration(displayName = "#CTL_OBFHeadingsAction",
preferredID = "OBFHeadingsTopComponent")
public final class OBFHeadingsTopComponent extends TopComponent 
    implements ExplorerManager.Provider  {

    public OBFHeadingsTopComponent() {
        ActionMap map = this.getActionMap (); 
        map.put(DefaultEditorKit.copyAction, ExplorerUtils.actionCopy(manager)); 
        map.put(DefaultEditorKit.cutAction,ExplorerUtils.actionCut(manager)); 
        map.put(DefaultEditorKit.pasteAction, ExplorerUtils.actionPaste(manager));        
        associateLookup (ExplorerUtils.createLookup (manager, map));

        initComponents();
        setName(NbBundle.getMessage(OBFHeadingsTopComponent.class, "CTL_OBFHeadingsTopComponent"));
        setToolTipText(NbBundle.getMessage(OBFHeadingsTopComponent.class, "HINT_OBFHeadingsTopComponent"));
        
        view.addKeyListener(new KeyAdapter() {

            @Override
            public void keyPressed(KeyEvent e) {
                if(e.getKeyCode() == KeyEvent.VK_DELETE)
                    deteteNodes();
            }
        });
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        listPanel = new javax.swing.JPanel();
        jMoveUp = new javax.swing.JButton();
        jMoveDown = new javax.swing.JButton();
        jDecLevel = new javax.swing.JButton();
        jIncLevel = new javax.swing.JButton();
        jAddHeading = new javax.swing.JButton();
        jDeleteHeading = new javax.swing.JButton();
        jFixPointer = new javax.swing.JButton();

        listPanel.setLayout(new java.awt.BorderLayout());

        jMoveUp.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/openbookformat/heading/bullet_arrow_up.png"))); // NOI18N
        org.openide.awt.Mnemonics.setLocalizedText(jMoveUp, org.openide.util.NbBundle.getMessage(OBFHeadingsTopComponent.class, "OBFHeadingsTopComponent.jMoveUp.text")); // NOI18N
        jMoveUp.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMoveUpActionPerformed(evt);
            }
        });

        jMoveDown.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/openbookformat/heading/bullet_arrow_down.png"))); // NOI18N
        org.openide.awt.Mnemonics.setLocalizedText(jMoveDown, org.openide.util.NbBundle.getMessage(OBFHeadingsTopComponent.class, "OBFHeadingsTopComponent.jMoveDown.text")); // NOI18N
        jMoveDown.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMoveDownActionPerformed(evt);
            }
        });

        jDecLevel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/openbookformat/heading/bullet_arrow_left.png"))); // NOI18N
        org.openide.awt.Mnemonics.setLocalizedText(jDecLevel, org.openide.util.NbBundle.getMessage(OBFHeadingsTopComponent.class, "OBFHeadingsTopComponent.jDecLevel.text")); // NOI18N
        jDecLevel.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jDecLevelActionPerformed(evt);
            }
        });

        jIncLevel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/openbookformat/heading/bullet_arrow_right.png"))); // NOI18N
        org.openide.awt.Mnemonics.setLocalizedText(jIncLevel, org.openide.util.NbBundle.getMessage(OBFHeadingsTopComponent.class, "OBFHeadingsTopComponent.jIncLevel.text")); // NOI18N
        jIncLevel.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jIncLevelActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(jAddHeading, org.openide.util.NbBundle.getMessage(OBFHeadingsTopComponent.class, "OBFHeadingsTopComponent.jAddHeading.text")); // NOI18N
        jAddHeading.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jAddHeadingActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(jDeleteHeading, org.openide.util.NbBundle.getMessage(OBFHeadingsTopComponent.class, "OBFHeadingsTopComponent.jDeleteHeading.text")); // NOI18N
        jDeleteHeading.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jDeleteHeadingActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(jFixPointer, org.openide.util.NbBundle.getMessage(OBFHeadingsTopComponent.class, "OBFHeadingsTopComponent.jFixPointer.text")); // NOI18N
        jFixPointer.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jFixPointerActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(listPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE)
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                            .addComponent(jFixPointer, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                            .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                                .addComponent(jAddHeading)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(jDeleteHeading)
                                .addGap(18, 18, 18)
                                .addComponent(jMoveUp)))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(jMoveDown)
                        .addGap(57, 57, 57)
                        .addComponent(jDecLevel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jIncLevel)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(listPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 238, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(jAddHeading)
                        .addComponent(jDeleteHeading))
                    .addComponent(jDecLevel)
                    .addComponent(jIncLevel)
                    .addComponent(jMoveUp)
                    .addComponent(jMoveDown))
                .addGap(18, 18, 18)
                .addComponent(jFixPointer)
                .addContainerGap())
        );

        view = new ListView();
        listPanel.add(view);
    }// </editor-fold>//GEN-END:initComponents

    private void jAddHeadingActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jAddHeadingActionPerformed
        AddHeadingDialog dialog = new AddHeadingDialog(null, true);
        dialog.setVisible(true);
        if(dialog.isOkSelected() && bookProp.getTotalNumberOfChapter() > 0) {
            //determine the location of where to add these headings
            int where = bookProp.getTotalNumberOfHeadings();
            Node[] sNodes = manager.getSelectedNodes();
            if(sNodes.length > 0) {
                where = bookProp.indexOf(sNodes[sNodes.length -1].
                    getLookup().lookup(HeadingPointer.class)) + 1;
            }              
            
            //parsing and adding
            String[] headings = dialog.getText().split("\n");
            int level = 1;
            for(int i = 0; i < headings.length; i++) {
                level = 1;
                while(headings[i].charAt(level - 1) == '\t')
                    level++;
                bookProp.insertHeading(where + i, 
                        new HeadingPointer(bookProp.getChapter(0), -1, level,
                        headings[i].trim()));
            }
        }
    }//GEN-LAST:event_jAddHeadingActionPerformed

    private void jMoveUpActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMoveUpActionPerformed
        try {
            Node[] sNodes = manager.getSelectedNodes();
            if(sNodes.length == 0 || bookProp.indexOf(sNodes[0].
                    getLookup().lookup(HeadingPointer.class)) == 0)
                return;

            HeadingPointer pointer = null;
            int[] sIdx = new int[sNodes.length];
            for(int i = 0; i < sNodes.length; i++) {
                pointer =  sNodes[i].getLookup().lookup(HeadingPointer.class);
                sIdx[i] = bookProp.moveHeadingUp(pointer);
            }

            List<Node> nodes = new ArrayList(Arrays.asList( manager.getRootContext().getChildren().getNodes() ) );
            Node[] newSelNodes = new Node[sNodes.length];
            for(int i = 0; i < sIdx.length; i++) {
                newSelNodes[i] = nodes.get(sIdx[i]);
            }

            manager.setSelectedNodes(new Node[0]);
            manager.setSelectedNodes(newSelNodes);
            view.requestFocus();
        } catch (PropertyVetoException ex) {
            Exceptions.printStackTrace(ex);
        }
    }//GEN-LAST:event_jMoveUpActionPerformed

    private void jMoveDownActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMoveDownActionPerformed
        try {
            Node[] sNodes = manager.getSelectedNodes();
            System.out.println("sNodes.length:" + sNodes.length);
            System.out.println(bookProp.indexOf(sNodes[sNodes.length -1].
                    getLookup().lookup(HeadingPointer.class)));
            System.out.println(bookProp.getTotalNumberOfHeadings());
            if(sNodes.length == 0 || bookProp.indexOf(sNodes[sNodes.length -1].
                    getLookup().lookup(HeadingPointer.class)) == bookProp.getTotalNumberOfHeadings() -1)
                return;

            System.out.println("OK to move down..");
            HeadingPointer pointer = null;
            int[] sIdx = new int[sNodes.length];
            for (int i = sNodes.length -1; i > -1; i--) {
                pointer = sNodes[i].getLookup().lookup(HeadingPointer.class);
                sIdx[i] = bookProp.moveHeadingDown(pointer);
            }

            List<Node> nodes = new ArrayList(Arrays.asList( manager.getRootContext().getChildren().getNodes() ) );
            Node[] newSelNodes = new Node[sNodes.length];
            for(int i = 0; i < sIdx.length; i++) {
                newSelNodes[i] = nodes.get(sIdx[i]);
            }

            manager.setSelectedNodes(new Node[0]);
            manager.setSelectedNodes(sNodes);
            view.requestFocus();
        } catch (PropertyVetoException ex) {
            Exceptions.printStackTrace(ex);
        }
    }//GEN-LAST:event_jMoveDownActionPerformed

    private void jDeleteHeadingActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jDeleteHeadingActionPerformed
        deteteNodes();
    }//GEN-LAST:event_jDeleteHeadingActionPerformed

    private void deteteNodes() {
        Node[] sNodes = manager.getSelectedNodes();
        for(int i = sNodes.length -1; i >= 0; i--) {
            bookProp.deleteHeading(sNodes[i].getLookup().lookup(HeadingPointer.class));
        }
    }
    private void jDecLevelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jDecLevelActionPerformed
        Node[] sNodes = manager.getSelectedNodes();
        int level = -1;
        for(int i = 0; i < sNodes.length; i++) {
            level = sNodes[i].getLookup().lookup(HeadingPointer.class).getLevel();
//            System.out.print("Level: " + level + " -- > ");
            level--;
            if(level < 1)
                level = 1;
            sNodes[i].getLookup().lookup(HeadingPointer.class).setLevel(level);
//            System.out.println(level);
        }
    }//GEN-LAST:event_jDecLevelActionPerformed

    private void jIncLevelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jIncLevelActionPerformed
        Node[] sNodes = manager.getSelectedNodes();
        int level = -1;
        for(int i = 0; i < sNodes.length; i++) {
            level = sNodes[i].getLookup().lookup(HeadingPointer.class).getLevel();
//            System.out.print("Level: " + level + " -- > ");
            level++;
            sNodes[i].getLookup().lookup(HeadingPointer.class).setLevel(level);
//            System.out.println(level);
        }
    }//GEN-LAST:event_jIncLevelActionPerformed

    private void jFixPointerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jFixPointerActionPerformed
        int ptrIdx = 0;
        String heading = null;
        HeadingTag tag =  null;
        boolean found = false;
        
        //Get Chapter Nodes
        Node[] sNodes = OBFChaptersViewerTopComponent.getDefault().
                getExplorerManager().getRootContext().getChildren().getNodes();
        List<HeadingPointer> pointers = bookProp.getHeadings();
        
        //find all the headings in the whole book.
        ArrayList<FindResult> r = new ArrayList<FindResult>();
        ArrayList<FindResult> tmp;
        FindUtil fu = new FindUtil(HeadingTag.TAG_PATTERN, "");
        fu.setRegExp(true);
        fu.setUseVariables(true);
        for(int i = 0; i < bookProp.getTotalNumberOfChapter(); i++) {
            r = fu.find(sNodes[i].getLookup().lookup(EditorCookie.class));
            //Loop over found heading and update the pointers
            for(int j = 0; j < r.size(); j++) {
                try {
                    tag = HeadingTag.parse(r.get(j).getFind());
                } catch (TagParsingException ex) {
                    Exceptions.printStackTrace(ex);
                }
                heading = tag.getToc_text() == null? tag.getText().toString():
                        tag.getToc_text().toString();
                
                found = false;
                while(!found && ptrIdx < pointers.size()){
                    if(pointers.get(ptrIdx).getHeading().equals(heading)) {
                        found = true;
                        pointers.get(ptrIdx).setOffset(r.get(j).getIndex());
                        pointers.get(ptrIdx).setLevel(tag.getLevel());
                        pointers.get(ptrIdx).setChapter(sNodes[i].getLookup().
                                lookup(Chapter.class));                       
                    }
                    ptrIdx++;
                }
            }
        }
        
        
    }//GEN-LAST:event_jFixPointerActionPerformed

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton jAddHeading;
    private javax.swing.JButton jDecLevel;
    private javax.swing.JButton jDeleteHeading;
    private javax.swing.JButton jFixPointer;
    private javax.swing.JButton jIncLevel;
    private javax.swing.JButton jMoveDown;
    private javax.swing.JButton jMoveUp;
    private javax.swing.JPanel listPanel;
    // End of variables declaration//GEN-END:variables
    @Override
    public void componentOpened() {
        instance = this;
        
        pResult = Utilities.actionsGlobalContext().lookupResult(Project.class);
        doResult = Utilities.actionsGlobalContext().lookupResult(DataObject.class);

        pResult.addLookupListener(new LookupListener() {
            @Override
            public void resultChanged(LookupEvent ev) {
                Collection c = ((Lookup.Result) ev.getSource()).allInstances();
                if(!c.isEmpty())
                    projectChanged((Project) c.iterator().next());
            }
        });

        doResult.addLookupListener(new LookupListener() {
            @Override
            public void resultChanged(LookupEvent ev) {
                Collection c = ((Lookup.Result) ev.getSource()).allInstances();
                if(!c.isEmpty())
                    projectChanged(FileOwnerQuery.getOwner(
                        ((DataObject) c.iterator().next()).getPrimaryFile()));
            }
        });

        projectChanged(OpenProjects.getDefault().getMainProject());
    }
    
    
    private void projectChanged(Project nProject) {
        if(nProject == null || nProject == project)
            return;

        //remove old observer
        if (project != null)
            bookProp.getLookup().lookup(Observable.class)
                .deleteObserver(headingsChildren);
        //switch project
        project = nProject;

        bookProp = project.getLookup().lookup(BookProp.class);
        headingsChildren = new HeadingsChildren(bookProp);
        if(headingsChildren != null) {
            //add new observer
            bookProp.getLookup().lookup(Observable.class).
                    addObserver(headingsChildren);
            manager.setRootContext(new AbstractNode(headingsChildren));
        }
    }

    @Override
    public void componentClosed() {
        // TODO add custom code on component closing
    }

    void writeProperties(java.util.Properties p) {
        // better to version settings since initial version as advocated at
        // http://wiki.apidesign.org/wiki/PropertyFiles
        p.setProperty("version", "1.0");
        // TODO store your settings
    }

    void readProperties(java.util.Properties p) {
        String version = p.getProperty("version");
        // TODO read your settings according to their version
    }
    
    @Override
    public ExplorerManager getExplorerManager() {
        return manager;
    }
    
    @Override
    protected void componentActivated() {
        ExplorerUtils.activateActions(manager, true);
    }

    @Override
    protected void componentDeactivated() {
        ExplorerUtils.activateActions(manager, false);
    }
    
    private ListView view;
    private Project project;
    private Lookup.Result pResult;
    private Lookup.Result doResult;
    private HeadingsChildren headingsChildren;
    private BookProp bookProp;
    private final ExplorerManager manager = new ExplorerManager();

    //TODO: This is a temp solution for getting an instance of this class
    private static OBFHeadingsTopComponent instance; 
    public static OBFHeadingsTopComponent getInstance() {
        return instance;
    }
    public BookProp getBookProp() {
        return bookProp;
    }
}
